using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using System.Collections;
using System.Windows.Controls.Primitives;
using System.Collections.Generic;
using System.Windows.Input;
using System;

namespace TextAdornment
{
    ///<summary>
    ///TextAdornment1 places red boxes behind all the "A"s in the editor window
    ///</summary>
    public class TextAdornment
    {
        IAdornmentLayer _layer;
        IWpfTextView _view;
        Brush _brush;
        Pen _pen;
        DTE dte;

        bool isConfigLoaded = false;
        Hashtable htbVariables = new Hashtable();
        List<TextAdornmentImage> listImages = new List<TextAdornmentImage>();
        VariableInputControl variableInputControl;
        CodeEditorControl codeEditorControl;
        CodePreviewControl codePreviewControl;
        IOService ioService;

        Button previewCodeBtn;

        public TextAdornment(IWpfTextView view, DTE dte)
        {
            _view = view;
            _layer = view.GetAdornmentLayer("TextAdornment1");
            
            //Listen to any event that changes the layout (text changes, scrolling, etc)
            _view.LayoutChanged += OnLayoutChanged;
            
            //Create the pen and brush to color the box behind the a's
            Brush brush = new SolidColorBrush(Color.FromArgb(0x20, 0x00, 0x00, 0xff));
            brush.Freeze();
            Brush penBrush = new SolidColorBrush(Colors.Red);
            penBrush.Freeze();
            Pen pen = new Pen(penBrush, 0.5);
            pen.Freeze();

            _brush = brush;
            _pen = pen;

            // set dte
            this.dte = dte;

            // initialize service
            ioService = new IOService();

            // draw the button to preview the generated code
            previewCodeBtn = new Button()
            {
                Content = "Preview Code",
                Cursor = Cursors.Arrow
            };
            previewCodeBtn.Click += OnPreviewCodeBtnClick;

            _view.ViewportHeightChanged += delegate { this.OnSizeChange(); };
            _view.ViewportWidthChanged += delegate { this.OnSizeChange(); };
        }

        public Hashtable HtbVariables 
        {
            get
            {
                return htbVariables;
            }
            set
            {
                htbVariables = value;
            }
        }

        public List<TextAdornmentImage> ListImages
        {
            get
            {
                return listImages;
            }
            set
            {
                listImages = value;
            }
        }

        public IOService IOService
        {
            get
            {
                return ioService;
            }
            set
            {
                ioService = value;
            }
        }

        /// <summary>
        /// On layout change add the adornment to any reformatted lines
        /// </summary>
        private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
        {
            if (dte.ActiveDocument == null)
            {
                return;
            }

            //clear the adornment layer of previous adornments
            _layer.RemoveAdornment(previewCodeBtn);

            // determine the path to configuration file
            var templateFilePath = dte.ActiveDocument.FullName;
            if (IOService.GetFileExtension(templateFilePath) != ".template")
            {
                return;
            }
            var spcFilePath = IOService.GetSPCFilePath(templateFilePath);

            // load configurations if the config file exists, return otherwise
            if (IOService.IsFileExisted(spcFilePath))
            {
                LoadConfiguration(spcFilePath);
            }
            else
            {
                return;
            }
            
            foreach (ITextViewLine line in e.NewOrReformattedLines)
            {
                this.CreateVisuals(line);
            }

            // display preview code button if it is instance
            if (IOService.IsInstanceTemplate(templateFilePath))
            {
                //Place the image in the top right hand corner of the Viewport
                Canvas.SetLeft(previewCodeBtn, _view.ViewportRight - 100);
                Canvas.SetTop(previewCodeBtn, _view.ViewportTop + 30);

                //add the image to the adornment layer and make it relative to the viewport
                _layer.AddAdornment(AdornmentPositioningBehavior.ViewportRelative, null, null, previewCodeBtn, null);
            }
            // clean up unecessary variables/codes in the hastable
            // ioService.CleanUpHashtable(ref htbVariables, _view.TextSnapshot.GetText());
        }

        public void OnSizeChange()
        {
            if (dte.ActiveDocument == null || ioService.GetFileExtension(dte.ActiveDocument.FullName) != ".template" ||
                !dte.ActiveDocument.ReadOnly)
            {
                return;
            }

            //clear the adornment layer of previous adornments
            _layer.RemoveAdornment(previewCodeBtn);

            //Place the image in the top right hand corner of the Viewport
            Canvas.SetLeft(previewCodeBtn, _view.ViewportRight - 100);
            Canvas.SetTop(previewCodeBtn, _view.ViewportTop + 30);

            //add the image to the adornment layer and make it relative to the viewport
            _layer.AddAdornment(AdornmentPositioningBehavior.ViewportRelative, null, null, previewCodeBtn, null);
        }

        private void LoadConfiguration(string spcFilePath)
        {
            // return if config has been loaded
            if (isConfigLoaded)
            {
                return;
            }

            // Load the SPC file
            ioService.LoadCodesAndVariables(spcFilePath, ref htbVariables);

            // set config loaded to true
            isConfigLoaded = true;
        }

        /// <summary>
        /// Within the given line add the scarlet box behind the a
        /// </summary>
        private void CreateVisuals(ITextViewLine line)
        {
            HighlightVariationPoints(line.Start, line.End);
        }

        private void HighlightVariationPoints(int start, int end)
        {
            if (start < end)
            {
                // get the text left
                string text = _view.TextSnapshot.GetText(new Microsoft.VisualStudio.Text.Span(start, end - start));

                // get the open bracket index
                int openAtIndex = text.IndexOf("@");
                if (openAtIndex < 0)
                {
                    return;
                }

                // get the closed bracket index
                int closeAtIndex = text.IndexOf("@", openAtIndex + 1);
                if (closeAtIndex < 0)
                {
                    return;
                }

                // validate the length of the variable
                if (closeAtIndex - openAtIndex - 1 <= 0)
                {
                    return;
                }

                // get the variable name
                var variableName = text.Substring(openAtIndex + 1, closeAtIndex - openAtIndex - 1);

                // highlight the variation point just found
                DrawHighlight(start, end, openAtIndex, closeAtIndex, variableName);
                
                // add new variable to the hashtable if applicable
                //if (!htbVariables.ContainsKey(variableName))
                //{
                //    ioService.AddNewCodeOrVariable(variableName, string.Empty, ref htbVariables);
                //}

                // recursively find and highlight other variation points
                HighlightVariationPoints(start + closeAtIndex + 1, end);
            }
        }

        private void DrawHighlight(int start, int end, int openAtIndex, int closeAtIndex, string variableName)
        {
            IWpfTextViewLineCollection textViewLines = _view.TextViewLines;
            SnapshotSpan span = new SnapshotSpan(_view.TextSnapshot, Microsoft.VisualStudio.Text.Span.FromBounds(start + openAtIndex, start + closeAtIndex + 1));
            Geometry g = textViewLines.GetMarkerGeometry(span);
            if (g != null)
            {
                GeometryDrawing drawing = new GeometryDrawing(_brush, _pen, g);
                drawing.Freeze();

                DrawingImage drawingImage = new DrawingImage(drawing);
                drawingImage.Freeze();

                TextAdornmentImage image = new TextAdornmentImage();
                image.MouseLeftButtonDown += OnLeftClick;
                image.Source = drawingImage;
                image.InnerText = variableName;
                
                // create tool tip for the image
                var toolTip = new ToolTip();
                toolTip.Background = new SolidColorBrush(Colors.LightGoldenrodYellow);
                toolTip.Foreground = new SolidColorBrush(Colors.Black);
                toolTip.HorizontalContentAlignment = HorizontalAlignment.Center;
                toolTip.VerticalContentAlignment = VerticalAlignment.Center;
                if (htbVariables[variableName] != null)
                {
                    toolTip.Content = htbVariables[variableName].ToString();
                }
                image.ToolTip = toolTip;
                image.MouseEnter += OnMouseEnter;

                // add image to list
                listImages.Add(image);

                //Align the image with the top of the bounds of the text geometry
                Canvas.SetLeft(image, g.Bounds.Left);
                Canvas.SetTop(image, g.Bounds.Top);

                _layer.AddAdornment(AdornmentPositioningBehavior.TextRelative, span, null, image, null);
            }
        }

        private void OnMouseEnter(object sender, MouseEventArgs e)
        {
            if (dte.ActiveDocument.ReadOnly)
            {
                return;
            }

            var targettedImage = sender as TextAdornmentImage;
            if (targettedImage != null)
            {
                targettedImage.ToolTip = null;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnLeftClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!dte.ActiveDocument.ReadOnly)
            {
                return;
            }

            var clickedImage = sender as TextAdornmentImage;
            if (clickedImage != null)
            {
                // get the content of the variable
                var innerValue = htbVariables[clickedImage.InnerText].ToString();
                var imagePosition = clickedImage.PointToScreen(new Point(0, 0));
                var clickedPosition = e.GetPosition(clickedImage);
                var popupPosition = new Point
                {
                    X = imagePosition.X + clickedPosition.X,
                    Y = imagePosition.Y + clickedPosition.Y
                };

                // show the input box to get value of variable
                if (htbVariables[clickedImage.InnerText] is Variable)
                {
                    variableInputControl = new VariableInputControl(clickedImage, this);
                    variableInputControl.Popup(popupPosition);
                }
                // show the code editor to get the code
                else if (htbVariables[clickedImage.InnerText] is ExtraCode)
                {
                    codeEditorControl = new CodeEditorControl(clickedImage, this);
                    codeEditorControl.Popup(popupPosition);
                }
            }  
        }

        private void OnPreviewCodeBtnClick(object sender, RoutedEventArgs args)
        {
            if (dte.ActiveDocument == null || ioService.GetFileExtension(dte.ActiveDocument.FullName) != ".template")
            {
                return;
            }

            string generatedCode = string.Empty;

            // determine the path to configuration file
            var templateFilePath = dte.ActiveDocument.FullName;
            var spcFilePath = IOService.GetSPCFilePath(templateFilePath);

            // determine the path to the code file
            var codeFilePath = IOService.GetCodeFilePath(spcFilePath);

            // delete the code file if it already exists
            IOService.DeleteFile(codeFilePath);

            // generate the code from the config file
            if (IOService.IsFileExisted(spcFilePath))
            {
                try
                {
                    IOService.GenerateCodeFromTempate(spcFilePath);
                    generatedCode = IOService.GetFileContent(codeFilePath);
                }
                catch
                {
                    generatedCode = "Error in the code. Please check";
                }
            }
            
            codePreviewControl = new CodePreviewControl(this, generatedCode);
            codePreviewControl.Popup();
        }
    }
}
